#include "Planet.h"

#include <iostream>

#include <NxForceFieldLinearKernel.h>
#include <NxForceFieldShape.h>
#include <NxSphereForceFieldShape.h>
#include <NxSphereShape.h>

#include <NxForceFieldDesc.h>
#include <NxForceFieldLinearKernelDesc.h>
#include <NxForceFieldShapeDesc.h>
#include <NxSphereForceFieldShapeDesc.h>
#include <NxSphereShapeDesc.h>
#include <NxActorDesc.h>
#include <NxBodyDesc.h>

#include "Renderer.h"
#include "PhysxLogic.h"

const float Planet::PLANET_GRAVITATIONFIELD_RADIUS = 1E+20;
const int Planet::POSITION_POINTS_STORAGE_MAXIMUM = 10000;
const int Planet::POSITION_POINTS_STORAGE_DISTANCE = 10;
const float Planet::DRAWING_SCALE_FACTOR = 1.0E-08;

Planet::Planet(const NxVec3 &pos, const std::string &name, float mass, float radius, float visualRadius, const NxVec3 &initialVelocity, const NxVec3 &initialAngularVelocity, const NxVec3 &color, DevilTexture *texture, NxScene *scene)
{
    this->isVisible = true;

    this->startPosition = pos;
    this->name = name;
    this->mass = mass;
    this->radius = radius;
    this->visualRadius = visualRadius;
    this->color = color;
    this->texture = texture;

    this->planetActor = NULL;
    this->gravitationField = NULL;
    this->lastPositionCount = 0;
    
    //actor creation
    NxSphereShapeDesc planetShape;
    planetShape.radius = this->radius;
    NxBodyDesc planetBodyDesc;
    planetBodyDesc.mass = this->mass;
    planetBodyDesc.linearVelocity = initialVelocity;
    planetBodyDesc.angularVelocity = initialAngularVelocity;
    planetBodyDesc.angularDamping = 0.0f;
    NxActorDesc planetDesc;	
    planetDesc.body = &planetBodyDesc;
    planetDesc.shapes.pushBack(&planetShape);
    planetDesc.globalPose.t = this->startPosition;
    planetDesc.userData = (void*)this;

    this->planetActor = scene->createActor(planetDesc);

    //forcefield creation
    float force = -6.6738480E-11 * this->mass;

    NxForceFieldLinearKernelDesc linearKernelDesc;
    linearKernelDesc.constant = NxVec3(force, 0.0f, 0.0f);
    linearKernelDesc.falloffQuadratic = NxVec3(1.0f, 0.0f, 0.0f);

    NxForceFieldLinearKernel *linearKernel;
    linearKernel = scene->createForceFieldLinearKernel(linearKernelDesc);

    NxSphereForceFieldShapeDesc gfSphereShapeDesc;
    gfSphereShapeDesc.radius = PLANET_GRAVITATIONFIELD_RADIUS;

    NxForceFieldDesc planetGravitationfieldDesc;
    planetGravitationfieldDesc.kernel = linearKernel;
    planetGravitationfieldDesc.actor = planetActor;
    planetGravitationfieldDesc.coordinates = NX_FFC_SPHERICAL;
    planetGravitationfieldDesc.rigidBodyType = NX_FF_TYPE_GRAVITATIONAL;
    planetGravitationfieldDesc.includeGroupShapes.pushBack(&gfSphereShapeDesc);

    this->gravitationField = scene->createForceField(planetGravitationfieldDesc);
}

Planet::~Planet()
{
    if(this->gravitationField != NULL)
    {
        NxForceFieldLinearKernel *linearKernel = (NxForceFieldLinearKernel*)this->gravitationField->getForceFieldKernel();
        PhysxLogic::physicsScene->releaseForceField(*(this->gravitationField));
        PhysxLogic::physicsScene->releaseForceFieldLinearKernel(*linearKernel);
    }
    if(this->planetActor != NULL)
        PhysxLogic::physicsScene->releaseActor(*(this->planetActor));
}

void Planet::Draw()
{
    float matrix[16];
    NxMat34 m = this->planetActor->getGlobalPose();
    m.t = m.t * DRAWING_SCALE_FACTOR;
    m.getColumnMajor44(matrix);
    glPushMatrix();
    if(texture != NULL && texture->IsLoaded() == true)
        texture->Use();
    glMultMatrixf(matrix);
    if(texture == NULL || texture->IsLoaded() == false)
        glColor3f(this->color.x, this->color.y, this->color.z);
    else
        glColor3f(1.0f, 1.0f, 1.0f);
    glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
    gluSphere(Renderer::GetQuadric(), this->visualRadius, 64, 64);
    glPopMatrix();

    //path visualization
    glColor3f(this->color.x, this->color.y, this->color.z);
    glLineWidth(1.0f);
    
    NxVec3 *pointNow = NULL;
    NxMat34 mat;
    glBegin(GL_LINE_STRIP);
    for(std::list<NxVec3>::iterator it = this->lastPositions.begin(); it != this->lastPositions.end(); ++it)
    {
        pointNow = (&(*it));
        mat.t = *pointNow;
        mat.getColumnMajor44(matrix);
        glPushMatrix();
        glMultMatrixf(matrix);		
        glVertex3f(pointNow->x,pointNow->y, pointNow->z);		
        glPopMatrix();
    }
    glEnd();
}

void Planet::Update()
{
    if(this->lastPositionCount++ == POSITION_POINTS_STORAGE_DISTANCE)
    {
        NxVec3 pos = this->planetActor->getGlobalPosition();
        pos = pos * DRAWING_SCALE_FACTOR;
        if(this->lastPositions.size() > 0)
        {
            std::list<NxVec3>::iterator lastPos = this->lastPositions.end();
            lastPos--;

            if(lastPos->x != pos.x || lastPos->y != pos.y || lastPos->z != pos.z)
            {
                this->lastPositions.push_back(pos);
            }
        }
        else
        {
            this->lastPositions.push_back(pos);
        }		
        if(this->lastPositions.size() > POSITION_POINTS_STORAGE_MAXIMUM)
            this->lastPositions.pop_front();
        
        this->lastPositionCount = 0;
    }	
}